#include <stdio.h>
#include "A_memory.h"
#include "A_memory_shell.h"

#if A_MEMORY_USE_SHELL == 1

static const A_mem_hard_item_t *flash_dev = NULL;
static const A_mem_area_item_t *part_dev = NULL;

#if A_MEMORY_USE_TRANSFER == 1  
static enum ym_code m_ym_send_begin(
    struct ym_ctx *ctx,
    uint8_t *buf,
    uint32_t len);
static enum ym_code m_ym_send_data(
    struct ym_ctx *ctx,
    uint8_t *buf,
    uint32_t len);
static enum ym_code m_ym_send_end(
    struct ym_ctx *ctx,
    uint8_t *buf,
    uint32_t len);

static enum ym_code d_ymodem_on_begin(
    struct ym_ctx *ctx,
    uint8_t *buf,
    uint32_t len);

static enum ym_code d_ymodem_on_data(
    struct ym_ctx *ctx,
    uint8_t *buf,
    uint32_t len);

static uint32_t m_file_total_size, m_file_cur_size;

static enum ym_code m_ym_send_begin(
    struct ym_ctx *ctx,
    uint8_t *buf,
    uint32_t len)
{
    char insert_0 = '\0';

	memset(buf, 0, len);
    m_file_cur_size = 0;
    if (flash_dev)
    {
        m_file_total_size = flash_dev->size;
        sprintf((char *)buf, "%s.bin%c%u", flash_dev->st, insert_0, m_file_total_size);
    }
    else if (part_dev)
    {
        m_file_total_size = part_dev->size;
        sprintf((char *)buf, "%s.bin%c%u", part_dev->st, insert_0, m_file_total_size);
    }
        
    return YM_CODE_SOH;
}

static enum ym_code m_ym_send_data(
    struct ym_ctx *ctx,
    uint8_t *buf,
    uint32_t len)
{
    uint32_t tx_len;
	
	memset(buf, 0, len);
    if ((m_file_cur_size + len) > m_file_total_size)
    {
        tx_len = m_file_total_size - m_file_cur_size;
    }
	else
	{
		tx_len = len;
	}

    if (flash_dev)
    {
        flash_dev->Read(m_file_cur_size, (uint8_t *)buf, tx_len);
    }
    else if (part_dev)
    {
        Amem_Area_Read(AMEM_Crc32, (void *)part_dev->name,
                       (uint8_t *)buf, m_file_cur_size, tx_len);
    }
	m_file_cur_size += tx_len;
    if (tx_len < len)
    {
        memset(buf + tx_len, 0x1A, len - tx_len);
        ctx->stage = YM_STAGE_FINISHING;
    }

    return YM_CODE_SOH;
}

static enum ym_code m_ym_send_end(
    struct ym_ctx *ctx,
    uint8_t *buf,
    uint32_t len)
{
    memset(buf, 0, len);

    return YM_CODE_SOH;
}

static uint32_t dow_file_total_size, dow_file_cur_size;
static enum ym_code d_ymodem_on_begin(struct ym_ctx *ctx, uint8_t *buf, uint32_t len)
{
    char *file_name, *file_size;

    /* calculate and store file size */
    file_name = (char *)&buf[0];
    file_size = (char *)&buf[strlen(file_name) + 1];
    dow_file_total_size = atol(file_size);
    dow_file_cur_size = 0;

    if (flash_dev)
    {
        if (dow_file_total_size > flash_dev->size)
            return YM_CODE_CAN;
        flash_dev->Erase(flash_dev->base_addr, dow_file_total_size);
    }
    else if (part_dev)
    {
        if (dow_file_total_size > part_dev->size)
            return YM_CODE_CAN;
        Amem_Area_Erase(AMEM_Crc32, (void *)part_dev->name,
                        0, dow_file_total_size);
    }
    Ashell_print("download file name:%s, file size:%u\r\n", file_name, dow_file_total_size);
    return YM_CODE_ACK;
}

static enum ym_code d_ymodem_on_data(struct ym_ctx *ctx, uint8_t *buf, uint32_t len)
{

    if ((dow_file_cur_size + len) > dow_file_total_size)
        len = dow_file_total_size - dow_file_cur_size;
    /* write data of application to backup section  */
    if (flash_dev)
    {

        flash_dev->Write(dow_file_cur_size, buf, len);
    }
    else if (part_dev)
    {
        Amem_Area_Write(AMEM_Crc32, (void *)part_dev->name,
                        buf, dow_file_cur_size, len);
    }

    dow_file_cur_size += len;
    if (dow_file_cur_size > dow_file_total_size)
        return YM_CODE_CAN;

    return YM_CODE_ACK;
}

#endif



static void memory(uint8_t argc, char **argv)
{

#define __is_print(ch) ((unsigned int)((ch) - ' ') < 127u - ' ')
#define HEXDUMP_WIDTH 16
#define CMD_PROBE_INDEX 0
#define CMD_READ_INDEX 1
#define CMD_WRITE_INDEX 2
#define CMD_ERASE_INDEX 3
#define CMD_UPLOAD_INDEX 4
#define CMD_DOWNLOAD_INDEX 5
#define CMD_SHOW_INDEX 6
    int result;
    uint32_t dev_crc;
    size_t i = 0, j = 0;
    const A_mem_hard_item_t *tem_flash_dev = NULL;
    const char *help_info[] =
        {
            [CMD_PROBE_INDEX] =     "memory probe [dev_name|part_name]   - probe flash device or partition by given name",
            [CMD_READ_INDEX] =      "memory read addr size               - read 'size' bytes starting at 'addr'",
            [CMD_WRITE_INDEX] =     "memory write addr data1 ... data5   - write some bytes 'data' starting at 'addr'",
            [CMD_ERASE_INDEX] =     "memory erase addr size              - erase 'size' bytes starting at 'addr'",
            [CMD_UPLOAD_INDEX] =    "memory upload                       - upload memmory use ymodem",
            [CMD_DOWNLOAD_INDEX] =  "memory download                     - download memmory use ymodem",
            [CMD_SHOW_INDEX] =      "memory show                         - show memmory hard information and show area information",
        };

    if (argc < 2)
    {
        Ashell_print("Usage:\r\n");
        for (i = 0; i < sizeof(help_info) / sizeof(char *); i++)
        {
            Ashell_print("%s\r\n", help_info[i]);
        }
        Ashell_print("\r\n");
    }
    else
    {
        const char *operator= argv[1];
        uint32_t addr, size;

        if (!strcmp(operator, "probe"))
        {
            if (argc >= 3)
            {
                char *dev_name = argv[2];
                dev_crc = String_To_Crc32((char *)dev_name);
                if ((flash_dev = Find_Hard(dev_crc)) != NULL)
                {
                    part_dev = NULL;
                }
                else if ((part_dev = Find_Area(dev_crc)) != NULL)
                {
                    flash_dev = NULL;
                }
                else
                {
                    Ashell_print("Device %s NOT found. Probe failed.\r\n", dev_name);
                    flash_dev = NULL;
                    part_dev = NULL;
                }
            }

            if (flash_dev)
            {
                Ashell_print("Probed a flash device | %s | addr: %ld | len: %d |.\r\n", flash_dev->st,
                           flash_dev->base_addr, flash_dev->size);
            }
            else if (part_dev)
            {
                tem_flash_dev = Find_Hard(part_dev->hard_name);
                if (tem_flash_dev != NULL)
                    Ashell_print("Probed a flash partition | %s | flash_dev: %s | offset: %ld | len: %d |.\r\n",
                               part_dev->st, tem_flash_dev->st, part_dev->offset_addr, part_dev->size);
            }
            else
            {
                Ashell_print("No flash device or partition was probed.\r\n");
                Ashell_print("Usage: %s.\r\n", help_info[CMD_PROBE_INDEX]);
            }
        }
        else if(!strcmp(operator, "show"))
        {
            A_mem_hard_item_t *flash_tem = NULL;
            A_mem_area_item_t *part_tem = NULL;
            slist_t *tem_list = NULL;
            tem_list = Get_Hard_list();
            slist_for_each_entry(A_mem_hard_item_t, flash_tem, tem_list, list)
            {
                Ashell_print("flash device | %s | addr: %ld | len: %d |.\r\n", flash_tem->st,
                           flash_tem->base_addr, flash_tem->size);                
            }
            tem_list = Get_Area_list();
            slist_for_each_entry(A_mem_area_item_t, part_tem, tem_list, list)
            {
                char *hard_name = NULL;
                hard_name = Find_Hard(part_tem->hard_name)->st;
                if (hard_name == NULL)
                    continue;
                Ashell_print("flash area | %s | flash_dev: %s | offset: %ld | len: %d |.\r\n",
                             part_tem->st, hard_name, part_tem->offset_addr, part_tem->size);
            }                        
        }
        else
        {
            if (!flash_dev && !part_dev)
            {
                Ashell_print("No flash device or partition was probed. Please run 'memory probe'.\r\n");
                return;
            }
            if (!strcmp(operator, "read"))
            {
                if (argc < 4)
                {
                    Ashell_print("Usage: %s.\r\n", help_info[CMD_READ_INDEX]);
                    return;
                }
                else
                {
                    addr = strtol(argv[2], NULL, 0);
                    size = strtol(argv[3], NULL, 0);
                    uint8_t *data = Ram_Malloc(size);
                    if (data)
                    {
                        if (flash_dev)
                        {
                            result = flash_dev->Read(addr, data, size);
                        }
                        else if (part_dev)
                        {
                            result = Amem_Area_Read(AMEM_Crc32, (void *)part_dev->name, data, addr, size);
                        }
                        if (result >= 0)
                        {
                            Ashell_print("Read data success. Start from 0x%08X, size is %ld. The data is:\r\n", addr,
                                       size);
                            Ashell_print("Offset (h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\r\n");
                            for (i = 0; i < size; i += HEXDUMP_WIDTH)
                            {
                                Ashell_print("[%08X] ", addr + i);
                                /* dump hex */
                                for (j = 0; j < HEXDUMP_WIDTH; j++)
                                {
                                    if (i + j < size)
                                    {
                                        Ashell_print("%02X ", data[i + j]);
                                    }
                                    else
                                    {
                                        Ashell_print("   ");
                                    }
                                }
                                /* dump char for hex */
                                for (j = 0; j < HEXDUMP_WIDTH; j++)
                                {
                                    if (i + j < size)
                                    {
                                        Ashell_print("%c", __is_print(data[i + j]) ? data[i + j] : '.');
                                    }
                                }
                                Ashell_print("\r\n");
                            }
                            Ashell_print("\r\n");
                        }
                        Ram_Free(data);
                    }
                    else
                    {
                        Ashell_print("Low memory!\r\n");
                    }
                }
            }
            else if (!strcmp(operator, "write"))
            {
                if (argc < 4)
                {
                    Ashell_print("Usage: %s.\r\n", help_info[CMD_WRITE_INDEX]);
                    return;
                }
                else
                {
                    addr = strtol(argv[2], NULL, 0);
                    size = argc - 3;
                    uint8_t *data = Ram_Malloc(size);
                    if (data)
                    {
                        for (i = 0; i < size; i++)
                        {
                            data[i] = strtol(argv[3 + i], NULL, 0);
                        }
                        if (flash_dev)
                        {
                            result = flash_dev->Write(addr, data, size);
                        }
                        else if (part_dev)
                        {
                            result = Amem_Area_Write(AMEM_Crc32, (void *)part_dev->name, data, addr, size);
                        }
                        if (result >= 0)
                        {
                            Ashell_print("Write data success. Start from 0x%08X, size is %ld.\r\n", addr, size);
                            Ashell_print("Write data: ");
                            for (i = 0; i < size; i++)
                            {
                                Ashell_print("%d ", data[i]);
                            }
                            Ashell_print(".\r\n");
                        }
                        Ram_Free(data);
                    }
                    else
                    {
                        Ashell_print("Low memory!\r\n");
                    }
                }
            }
            else if (!strcmp(operator, "erase"))
            {
                if (argc < 4)
                {
                    Ashell_print("Usage: %s.\r\n", help_info[CMD_ERASE_INDEX]);
                    return;
                }
                else
                {
                    addr = strtol(argv[2], NULL, 0);
                    size = strtol(argv[3], NULL, 0);
                    if (flash_dev)
                    {
                        result = flash_dev->Erase(addr, size);
                    }
                    else if (part_dev)
                    {
                        result = Amem_Area_Erase(AMEM_Crc32, (void *)part_dev->name, addr, size);
                    }
                    if (result >= 0)
                    {
                        Ashell_print("Erase data success. Start from 0x%08X, size is %ld.\r\n", addr, size);
                    }
                }
            }
#if A_MEMORY_USE_TRANSFER == 1
            else if (!strcmp(argv[1], "upload"))
            {
                int res;
                struct ym_ctx rctx;
                Ashell_print("Please select a receive file and use Ymodem to receive.\r\n");
                res = ym_send_on_device(&rctx, m_ym_send_begin, m_ym_send_data, m_ym_send_end, 100);
                if (!res)
                {
                    Ashell_print("memory upload success.\r\n");
                }
                else
                {
                    Ashell_print("memory upload fail,code = %d.\r\n", res);
                }
            }
            else if (!strcmp(argv[1], "download"))
            {
                int err;
                struct ym_ctx rctx;
                Ashell_print("Please select a download file and use Ymodem to send.\r\n");
                err = ym_recv_on_device(&rctx, d_ymodem_on_begin, d_ymodem_on_data, NULL, 100);
                if (!err)
                {
                    Ashell_print("memory download success.\r\n");
                }
                else
                {
                    Ashell_print("memory download fail,code = %d.\r\n", err);
                }
            }
#endif
            else
            {
                Ashell_print("Usage:\r\n");
                for (i = 0; i < sizeof(help_info) / sizeof(char *); i++)
                {
                    Ashell_print("%s\r\n", help_info[i]);
                }
                Ashell_print("\r\n");
                return;
            }
            if (result < 0)
            {
                Ashell_print("This operate has an error. Error code: %d.\r\n", result);
            }
        }
    }
}

SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_MAIN), memory, memory, memory <read probe|read|erase|upload>:memory operate);
#endif
